← 返回首页

DeepSeek V4.1 全链路主时序:一条请求从压测端到首字返回的 57 步(四阶段)

从 aisbench 打出第一条请求,到首字返回、decode 循环、后台横切——启动加载、prefill、decode、后台四阶段 57 步一张时序图走完——认模型入编为步骤 3;补充节展开它的源头:models/__init__ 的 24 条模型注册表,载入权重前就定下哪个模型用哪套实现
读前须知:八个名词 30 秒(更多见文末术语速查)

1. 全景:57 步四阶段主时序

这张图回答一个问题:一条请求从压测端打进来,到首字返回、再到流式吐完,路上每一步发生了什么、发生在哪个角色手里。横向是十条泳道——从最左的压测客户端,穿过 tokenizer、引擎(EngineCore)、排班(BalanceScheduler)、KV 块管理,到 Worker、运行器、模型实现,最后落在 parallel_state(HCCL 通信)与 torch_npu(CANN 算子)两件底座上;纵向按时间推进,分四个阶段。

上游 vLLM vllm-ascend 底座 aisbench 压测端 用户/压测客户端 mm_preprocess.py L348 Tokenizer(ge t_tokenizer) vllm/v1/engine/core.py EngineCore(v1 .engine.core) patch_balance_schedule.py L103 BalanceSche duler(DPLB) vllm/v1/core/kv_cache_manager.py KVCacheMana ger(v1.core) worker/worker.py L126 NPUWorker 执行进程 model_runner_v1.py L363 NPUModelR unner 运行器 distributed/parallel_state.py parallel_s tate(HCCL) torch-npu 包(CANN) torch_np u(CANN算子) 用户/压测客户端 Tokenizer(ge EngineCore(v BalanceSched KVCacheManag NPUWorker NPUModelRunn AscendDeepse parallel_sta torch_npu(CA loop · 每层 DecoderLayer.forward L760 loop · 每步 decode(含 MTP 投机) 1. 初始化 torch_npu/CANN 与 HCCL 建链torch_npu.npu.set_device() [device/HCCL] 2. 拉起 API Server 与 EngineCore→Worker 进程组EngineCoreProc(core.py:1088)拉起 Worker [ZMQ·DP2×TP16] 4. 载入权重(多线程/lazy·NZ·W8A8)+细粒度切分NPUWorker.load_model() [FinegrainedTPConfig] 5. 建16卡通信组(TP16xDP2/EP32/SP)parallel_state [V2演进:PCP+DCP替代DSA-CP] 6. 预编译+静态内核+CPU绑核[ACL Graph](V1运行器,V2=新执行底座) 7. 专家初始摆放EplbWorker.get_init_expert_maps() [EPLB] 8. 显存 profiling → KV 块池与块表初始化determine_available_memory()+kv_cache_config 9. 探活 /health → 服务就绪(等待请求)[ready·warmup 完成] 10. HTTP 请求(3.5K 长文)POST /v1/chat/completions [OpenAI 兼容] 11. 文本转 token·并行分块+前缀缓存AutoTokenizer.encode() [FastToken] 12. token 序列进引擎队列·分配 request_idAsyncLLMEngine.add_request() [WAITING 状态] 13. 排班:本轮算哪些请求BalanceScheduler.schedule() [DPLB] 14. 前缀算过没有·命中即复用块get_computed_blocks() [prefix cache·块复用] 15. 没算过 → 全量 prefill[未命中·从 0 开始算] 16. 长文切块分轮算(4块·块大小可调)[chunkPrefill] 17. 分配 KV 块·写块表(命中块直接引用)allocate_slots() [block_table·水位检查] 18. 下发本批任务EngineCore.execute_model() [异步两步流水] 19. 转交运行器NPUWorker.execute_model() L764 20. 组装输入张量·attn_metadata·slot_mappinginput_batch [positions·block_table] 21. 开始前向(每卡只拿220行)DeepseekV4Model.forward L1094 [SP] 22. 归一化+位置编码一次算完rms_norm_cast L737 [MLAPO] 23. 进入稀疏注意力·prolog三段流水AscendDSACPImpl.forward L1620 [DSA-CP·record/wait] 24. 把每卡220行拼成全量3520(❶)sp_all_gather L22 25. 压缩+索引写KV·层间池化·下沉DRAM·topk常驻[KV池·kv offload·分配器·C8派生] 26. 互换 head 归属(❷)_restore_tp_head_layout L1942 27. 注意力只算 topk 相关块[SFA·KV打包C8·cache_dtype派生sfa_c8] 28. 求和后各取回本卡220行(❸)sp_reduce_scatter L29 29. 轮到 MoE(220行)DeepseekV4MoE.forward L383 30. token 送专家算完取回(一笔融合)dispatch_ffn_combine [fused_mc2·DFFC] 31. 路由专家FFN(gmm+SwiGLU融合·Triton下沉)[W8A8·gmmswigluquant] 32. 共享专家·完整副本零通信·双流并行[shared_expert_dp] 33. N 层走完 → logits[hidden_states → lm_head] 34. logits 处理(惩罚/温度/logit_bias)[logits_processor·分片归并] 35. 挑出首token_sample() L2817 [reduce_sample·分片去重] 36. 首 token + 块表状态回传[KV 块落位确认] 37. 首 token 上交 EngineCore[异步输出队列] 38. RequestOutput·首 token[detokenize 前] 39. token 转文字(流式)[FastToken·增量解码] 40. 首字到手(TTFT 终点)[TTFT 结束·进入 decode] 41. stop 条件检查(EOS/长度)→是否进 decode[stop_checker·请求收尾判定] 42. 每步 KV 追加:为新 token 分配槽位allocate_slots()/append_slots [block_table] 43. 下发 decode 批EngineCore.execute_model() 44. 转交运行器NPUWorker.execute_model L764→runner L2144 45. 前向+草稿连猜3字[MTP·dspark·dflash] 46. 验证草稿·留正确前缀RejectionSampler(ascend patch) 47. 一步出 1 至 4 token[accept 数 = 本轮产出] 48. 采样结果回传 Worker[token_ids·block 状态] 49. RequestOutput[增量 token 批次] 50. 输出 token[增量] 51. 增量文字(流式)[FastToken·SSE 推送] 52. EOS/达上限 → 释放 KV 块·请求收尾free_blocks() [块回收·metrics 打点] 53. 每600步统计专家热度EplbWorker.do_update() L46 54. 热专家加副本重摆+搬权重[FlashLB·D2D] 55. KV 跨实例搬运(PD 分离时)[mooncake/RD2H·kv_role·未启用即空转] 56. 指标/日志上报(TTFT/TPOT)[Prometheus·profiler·落盘] 57. 周期性探活 /health[keepalive·超时熔断] 阶段 0 · 启动加载(PD分离 kv_role/mooncake/RD2H 未启用) 阶段 1 · 执行 prefill(TTFT 主体,层内细节后续篇展开) 阶段 2 · decode 循环(细节后续篇展开) 阶段 3 · 后台横切(与前台并行) 3. 认模型:24 条架构名→实现类,注册进上游表DeepseekV4ForCausalLM → AscendDeepseekV4ForCausalLM实现类在 deepseek_v4/model.py · 登记在 models/__init__.py(步 4 查表取)AscendDeepseekV4ForCausalLMdeepseek_v4/model.py L1041注册于 models/__init__.pyModelRegistrymodels/__init__.pyarchitectures → 实现类ModelRegistry
图 1 · 全链路主时序:启动加载 → prefill → decode → 后台横切,57 步——认模型入编为步骤 3(宽图可左右滑动,点右上「全屏查看」整幅浏览)
全链路主时序 · 全屏视图——滚动查看全图;Esc 或点右侧按钮退出

阶段 0 · 启动加载(步 1 至 9):把"能算"准备好——torch_npu/HCCL 初始化、进程组拉起、认模型(步 3:24 条架构名→实现类注册进上游表)、权重载入(多线程/lazy·NZ·W8A8·细粒度切分)、16 卡通信组、预编译与绑核、专家初始摆放、显存 profiling 建 KV 块池,最后探活就绪。这一阶段的关键产物是 KV 池与块表——后面 prefill/decode 都在它上面分配。

阶段 1 · prefill(步 10 至 40):TTFT 的主体。HTTP 请求进来→文本转 token→引擎排队→排班→前缀缓存查命中(命中即复用 KV 块)→未命中则全量 prefill,长文切块分轮(图里 3.5K 长文切 4 块)→分配 KV 块写块表→下发执行→每卡只算 220 行的前向(步 21 至 33:SP 聚散、DSA-CP 三段流水、SFA 只算 topk 相关块、MoE 一笔融合、logits 分片归并)→采样挑首 token→一路回传→步 40 首字到手,TTFT 到此为止。

阶段 2 · decode 循环(步 41 至 52):每步先查 stop 条件,再为新 token 追加 KV 槽位;decode 批下发后,MTP 草稿连猜 3 字、验证器留正确前缀,一步产出 1 至 4 个 token;增量结果流式回传,EOS 或达上限时释放 KV 块收尾。

阶段 3 · 后台横切(步 53 至 57):与前台并行的几条侧线——每 600 步统计专家热度、热专家加副本重摆并搬权重(EPLB);PD 分离部署时 KV 跨实例搬运(本链路未启用即空转);指标上报与周期探活。

2. 阶段 0 的认模型一步:模型注册表

时序图把认模型画为步骤 3(浅蓝框),其后步骤全部顺延一位、全图 57 步——权重还没载之前,哪个模型用哪套实现就已经登记定死。翻 vllm-ascend 源码的 models/__init__.py,会看到 24 条"架构名→实现类"的登记——这正是"哪个模型、应该用哪个配置"的答案所在:

阶段 0 的认模型一步:模型注册表——哪个模型用哪套实现,载入权重前就定了 步骤 2 · 拉起进程组 EngineCore→Worker(ZMQ·DP2×TP16) 步骤 3 · 认模型(图 1 同名步骤) vLLM 扫 vllm.general_plugins 入口点 (setup.py:524 登记 ascend_model 钩子) → models/__init__.py 24 条,注册进上游 步骤 4 · 载入权重 NPUWorker.load_model() 按 config.json 的 architectures 字段 查上游 ModelRegistry 注册表 → AscendDeepseekV4ForCausalLM 实现类定,步骤 21 至 33 算子路径才定 models/__init__.py 注册表(24 条,节选) DeepseekV4ForCausalLM → deepseek_v4.model:AscendDeepseekV4ForCausalLM GlmMoeDsaForCausalLM → deepseek_mtp:AscendGlmMoeDsaForCausalLM Qwen3DSparkModel → qwen3_dspark:AscendQwen3DSparkForCausalLM KimiLinearForCausalLM(别名 KimiK3 同指) → kimi_k3:AscendKimiLinearForCausalLM DeepSeekV4MTPModel / DeepSeekMTPModel → deepseek_v4.mtp / deepseek_mtp 的 MTP 实现 全表 24 条:Kimi、Qwen、GLM、DeepSeek 各系 登记之后还有第二层选择 attention 后端:platform.py 按 hf_text_config 字段(如 index_topk) 决定注意力走哪套实现 读法:左列=阶段 0 的三步;右框=注册表节选与第二层选择;登记是一次性的,步骤 4 只查表。
图 2 · 阶段 0 放大:认模型一步(图 1 的步骤 3)——入口点→24 条注册表→查表取实现类
# setup.py:519-526 —— 插件把自己挂进 vLLM 的两排钩子
entry_points={
    "vllm.platform_plugins": ["ascend = vllm_ascend:register"],
    "vllm.general_plugins": [
        "ascend_kv_connector = vllm_ascend:register_connector",
        "ascend_model_loader = vllm_ascend:register_model_loader",
        "ascend_service_profiling = vllm_ascend:register_service_profiling",
        "ascend_model = vllm_ascend:register_model",   # ← 认模型挂在这
    ],
}

# vllm_ascend/__init__.py:100-105 —— 入口点进来后的包装
def register_model():
    from .models import register_model
    register_model()

# vllm_ascend/models/__init__.py —— 24 条登记(节选)
ModelRegistry.register_model(
    "DeepseekV4ForCausalLM",
    "vllm_ascend.models.deepseek_v4.model:AscendDeepseekV4ForCausalLM")
ModelRegistry.register_model(
    "KimiLinearForCausalLM",
    "vllm_ascend.models.kimi_k3:AscendKimiLinearForCausalLM")
ModelRegistry.register_model(
    "GlmMoeDsaForCausalLM",
    "vllm_ascend.models.deepseek_mtp:AscendGlmMoeDsaForCausalLM")
ModelRegistry.register_model(
    "Qwen3DSparkModel",
    "vllm_ascend.models.qwen3_dspark:AscendQwen3DSparkForCausalLM")
ModelRegistry.register_model(
    "DeepSeekV4MTPModel", "vllm_ascend.models.deepseek_v4.mtp:DeepSeekV4MTP")
# 全表 24 条;KimiK3ForCausalLM 是 KimiLinear 的兼容别名,指同一实现

# 上游 vllm/model_executor/models/registry.py:1121 —— 注册语义
# model_cls 走 "<module>:<class>" 字符串懒导入;同名架构直接覆盖:
#   "Model architecture %s is already registered, and will be
#    overwritten by the new model class %s."

# 触发点(每进程各一遍,plugins_loaded 防重):
#   vllm/engine/arg_utils.py:869        EngineArgs.__post_init__
#   vllm/v1/engine/core.py:125          EngineCore.__init__
#   vllm/v1/worker/worker_base.py:269   WorkerBase.__init__

机制链条四步(源码逐行核对过):①插件安装时 setup.py 把 ascend_model = vllm_ascend:register_model 登记进 vLLM 的 general_plugins 入口点;②触发点有三处、每个进程各执行一遍——前端解析引擎参数时(EngineArgs.__post_init__,arg_utils.py:869)、EngineCore 初始化时(v1/engine/core.py:125,源码注释原话"plugins need to be loaded at the engine/scheduler level too")、每卡 Worker 初始化时(v1/worker/worker_base.py:269),plugins_loaded 标志保证每进程只跑一次;③包装函数执行 models/__init__.py 里的 24 条 ModelRegistry.register_model——注册的是 <module>:<class> 字符串、用到时才真正 import(懒导入,防 fork 子进程重复初始化 CUDA),且同名架构直接被新类覆盖(registry.py 源码注释"overwritten by the new model class")——插件实现类就是这样顶掉上游默认的;④时序图步骤 4 载入权重时,上游按权重 config.json 的 architectures 字段查这张表——查到 AscendDeepseekV4ForCausalLM,后续步骤 21 至 33 的算子路径由此定型。登记之后还有第二层选择:attention 后端由 platform.py 按 hf_text_config 的字段(如 index_topk)决定走哪套注意力实现。

排查提示:模型没走上预期优化路径时,先查这两层

3. 术语速查

术语本文语境下的含义
EngineCore / Worker / ModelRunnervLLM 上游执行三级:引擎核心进程→每卡执行进程(NPUWorker)→每卡运行器(NPUModelRunner)
BalanceScheduler(DPLB)排班器——DP 侧负载均衡,决定本轮算哪些请求
DSA-CPDeepSeek 稀疏注意力 + 序列并行的通信模式(算前聚、算后散、head 归属互换)
MTP / dspark / dflash投机解码草稿头(MTP);dspark、dflash 为两种草稿实现名
EPLB专家并行负载均衡——按热度给热专家加副本并重摆
prefix cache / chunkPrefill前缀 KV 块复用 / 长 prefill 切块分轮执行
C8SFA 稀疏的 KV 存储块模式(enable_sparse_sfa_c8 / li_c8,随模型是否走 SFA 派生,ascend_config.py)
MLAPOMLA 相关融合优化开关(enable_mlapo,默认开)——图中步 21 归一化+cast 融合属它
fused_mc2 / DFFCMoE 大融合开关 / DispatchFFNCombine 算子(dispatch+专家 FFN+combine 一体)
TTFT / TPOT首字延迟 / 每字延迟——压测两端的两个核心读数

来源:时序主图为方案 B 稿件收录并经三项清理——口径、悬挂引用、行号/符号逐条对本地源码核验订正(13 处,泳道内 L348/L103/L363/L1041 与步 28/52 原本即精确命中);mm_preprocess.py 全路径 models/deepseek_v4/mm_preprocess.py、patch_balance_schedule.py 全路径 patch/platform/patch_balance_schedule.py(泳道小字限宽用短名);补充节与名词定义以本地 vllm-ascend 源码实测为准——setup.py:519-526(入口点)、vllm_ascend/__init__.py:100-105(包装)、vllm_ascend/models/__init__.py(24 条注册表)、ascend_config.py:481 与 753-758(MLAPO、C8);上游 vllm 侧 vllm/plugins/__init__.py(load_general_plugins)、v1/engine/core.py:125、v1/worker/worker_base.py:269、model_executor/models/registry.py:1121(register_model 覆盖语义)· 行号以当期源码为准

华杉的技术博客 · 苏ICP备2026063680号-1
首页